package com.example.lockLearning;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ✅ await()：
 * 当前线程放弃锁，并进入等待队列；
 * 线程就“睡着了”，直到别的线程 signal()；
 * 被唤醒后，它会重新去竞争 lock，如果拿到了，才会继续执行。
 * <p>
 * ✅ signal()：
 * 唤醒在这个 Condition 上等待的线程中的一个（随机）；
 * 被唤醒的线程不会立刻执行，它需要重新抢到 lock 才能醒过来继续跑。
 * <p>
 * await() 和 signal() 不是对“线程”本身直接操作，而是基于 Condition 的“等待队列”来管理线程挂起与唤醒的。
 */

/**
 * 一个锁：lock（唯一）
 * ├── Condition notFull   → 队列：等待“可放入”的线程
 * └── Condition notEmpty  → 队列：等待“可取出”的线程
 * <p>
 * ✔ 生产者调用 notFull.await()  → 被加入 notFull 队列
 * ✔ 消费者调用 notEmpty.await() → 被加入 notEmpty 队列
 * ✔ signal() 只能唤醒调用同一个 Condition.await() 的线程
 */
public class Condition1 {

    /**
     * 机制	        作用
     * lock	        保证同一时间只有一个线程执行临界区
     * Condition	控制不同线程在不同条件下等待与唤醒
     */

    /**
     * 一个 Lock 可以管理多个 Condition，每个 Condition 就像一个“等待理由”不同的等待队列。
     */
    private int buffer = -1;// -1 表示空
    /**
     * 你创建的是 ReentrantLock lock = new ReentrantLock()；
     * 这把锁是唯一的，无论是生产者线程还是消费者线程，都必须竞争这同一把锁才能执行方法；
     * 当一个线程调用 lock.lock() 成功后，其他线程在尝试 lock.lock() 时会被阻塞，直到锁被释放。
     */
    private final Lock lock = new ReentrantLock();// 锁
    /**
     * 虽然来自同一个锁，但它们是两个不同的条件队列！
     */
    /**
     * 不满的作用为：放入产品
     * 给生产者用的
     */
    private final Condition notFull = lock.newCondition();// 缓冲区不满
    /**
     * 不空的作用为：拿出产品
     * 给消费者用的
     */
    private final Condition notEmpty = lock.newCondition();// 缓冲区不空

    /**
     * 放入
     *
     * @param value
     * @throws InterruptedException
     */
    public void put(int value) throws InterruptedException {
        /**
         * 当线程调用 lock.lock() 后，它就成为该锁的“持有者”；
         * 其他线程如果也调用 lock.lock()，就会阻塞，直到当前线程释放锁；
         * 所以这段 put() 方法体中加了锁，意味着其他线程（包括消费者的 take() 方法）在未获得锁前不能进入。
         */
        lock.lock();// 上锁
        try {
            /**
             * 当前需要放入产品，如果产品不为-1，就需要使放入产品等待，因为此时为不空，即已经存在产品了
             */
            while (buffer != -1) {
                /**
                 * 放入产品等待
                 */
                notFull.await();// 如果有元素，就等待“缓冲区不满”
                /**
                 * 当前线程调用 await()；
                 * 它自动释放锁（否则其他线程永远没法获得锁）；
                 * 当前线程进入 notFull 的等待队列，进入等待状态；
                 * 等待被唤醒（signal）后，会重新去竞争 lock；
                 * 如果成功拿到锁，才会继续执行后面的代码。
                 */
            }
            buffer = value;
            System.out.println("生产者放入：" + value);
            /**
             * 激活拿出产品
             */
            notEmpty.signal();// 通知消费者“缓冲区不空”
            /**
             * 找出一个在 notEmpty 上等待的线程（如果有）；
             * 把它从“等待队列”移到“同步队列”，准备参与锁竞争；
             * 被唤醒的线程不会立刻执行，它要等当前线程释放锁；
             * 等被唤醒线程重新拿到锁，它才从 await() 返回继续执行。
             */
        } finally {
            lock.unlock();// 解锁
        }
    }

    /**
     * 拿出
     *
     * @return
     * @throws InterruptedException
     */
    public int take() throws InterruptedException {
        lock.lock();// 上锁
        try {
            /**
             * 当前需要拿出产品，如果产品为-1，就需要使拿出产品等待，因为此时为空，即已经没有产品了
             */
            while (buffer == -1) {
                /**
                 * 拿出产品等待
                 */
                notEmpty.await();// 如果空，就等待“缓冲区不空”
            }
            int value = buffer;
            buffer = -1;
            System.out.println("消费者取出：" + value);
            /**
             * 激活放入产品
             */
            notFull.signal();// 通知生产者“缓冲区空了”
            return value;
        } finally {
            lock.unlock();// 解锁
        }
    }

}

class Conditino1Test {
    public static void main(String[] args) {

        Condition1 c1 = new Condition1();

        /**
         * 通过不同的 Condition 对不同的线程进行控制
         * 问题	                        结论
         * 1. 一个锁，线程互斥？	        ✔️ 是的，生产者和消费者必须抢这同一把锁
         * 2. 用不同 Condition 控制线程？	✔️ 是的，虽然共用一把锁，但各在线程各自的 Condition 上等待/唤醒
         */

        /**
         * 哪个线程调用了哪个 Condition 的 await()，它就会进入这个 Condition 的“等待队列”中，直到被 signal() 或 signalAll() 唤醒。
         * 这就是 Condition 的本质：条件队列（等待队列）！
         */

        /**
         * 生产者线程
         */
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    /**
                     * 生产者获取放入锁
                     */
                    c1.put(i);
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();

        /**
         * 消费者线程
         */
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    /**
                     * 消费者获取拿出锁
                     */
                    c1.take();
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();

    }
}
